using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace Neo.MetaModel
{
	public enum IdMethod
	{
		IdBroker,
		Native,
		Guid,
		None
	}

    [Serializable]
	public class Entity 
	{
		//--------------------------------------------------------------------------------------
		//	constructor
		//--------------------------------------------------------------------------------------

		private Model		model;
		private ArrayList	attributes;
		private Hashtable	attributeMap;
		private ArrayList	relationships;

		private String		subPackageName;
		private String		className;
		private String		baseClassName;
		private String		defaultProperty;
		
		private String		tableName;
		private IdMethod	idMethod;		
		private bool		manyToMany;
        private bool        skip;
        private bool        forceOverwrite;
        private String      constraint;

		public Entity(Model aModel)
		{
			model = aModel;
			attributes = new ArrayList();
			attributeMap = new Hashtable();
			relationships = new ArrayList();
		}

		//--------------------------------------------------------------------------------------
		//	accessors
		//--------------------------------------------------------------------------------------

		public Model Model
		{
			get { return model ; }
		}


		public void AddAttribute(EntityAttribute anAttribute)
		{
			attributes.Add(anAttribute);
			attributeMap.Add(anAttribute.ColumnName, anAttribute);
		}

		public void RemoveAttribute(EntityAttribute anAttribute)
		{
			attributes.Remove(anAttribute);
			attributeMap.Remove(anAttribute.ColumnName);
		}

        public EntityAttribute GetAttributeForColumnName(string columnName)
        {
            return attributeMap[columnName] as EntityAttribute;
        }

		public ICollection Attributes
		{
			get { return attributes; }
		}


		public void AddRelationship(EntityRelationship aRelationship)
		{
			relationships.Add(aRelationship);
		}

		public void RemoveRelationship(EntityRelationship aRelationship)
		{
			relationships.Remove(aRelationship);
		}

		public ICollection Relationships
		{
			get { return relationships; }
		}

        public ICollection ParentRelationships
        {
            get
            {
                ArrayList list = new ArrayList();
                foreach (EntityRelationship relationship in relationships)
                {
                    if (relationship.Direction == RelDirection.Parent)
                        list.Add(relationship);
                }
                return list;
            }
        }

        public ICollection ChildRelationships
        {
            get
            {
                ArrayList list = new ArrayList();
                foreach (EntityRelationship relationship in relationships)
                {
                    if (relationship.Direction == RelDirection.Child)
                        list.Add(relationship);
                }
                return list;
            }
        }

		public string SubPackageName
		{
			set { subPackageName = value; }
			get { return subPackageName; }
		}

		public string ClassName
		{
			set { className = value; }
			get { return className; }
		}

        public string Constraint
        {
            get { return constraint; }
            set { constraint = value; }
        }

		public bool IsManyToManyTable
		{
			set { manyToMany = value; }
			get { return manyToMany; }
		}

        public bool Skip
        {
            set { skip = value; }
            get { return skip; }
        }

        public bool ForceOverwrite
        {
            set { forceOverwrite = value; }
            get { return forceOverwrite; }
        }

		public string BaseClassName
		{
			set { baseClassName = value; }
			get { return baseClassName; }
		}

		public string DefaultProperty 
		{
			get { return defaultProperty; }
			set { defaultProperty = value; }
		}
		
		public string TableName
		{
			set { tableName = value; }
			get { return tableName; }
		}

		public IdMethod IdMethod
		{
			set { idMethod = value; }
			get { return idMethod; }
		}
		
		public int AttributeCount
		{
			get
			{
				int i = 0;
				foreach(EntityAttribute att in Attributes)
				{
					if (att.IsHidden || att.IsPkColumn || att.IsSQLTextColumn)
					{
						
					}
					else
					{
						i++;
					}
				}
				return i;
			}
		}
		
		//--------------------------------------------------------------------------------------
		//	derived properties
		//--------------------------------------------------------------------------------------

		public string Namespace 
		{
			get
			{
				string ns = model.Namespace;
				if(SubPackageName != null)
					ns += "." + SubPackageName;
				return ns;
			}
            set
            {
                model.Namespace = value;
            }
		}

		public ICollection UsedNamespaces
		{
			get
			{
				ArrayList namespaces;

				namespaces = new ArrayList();
				foreach(EntityRelationship r in Relationships)
				{
					if(namespaces.Contains(r.ForeignEntity.Namespace) == false)
						namespaces.Add(r.ForeignEntity.Namespace);
				}
				return namespaces;
			}
		}

		public ICollection PkColumns
		{
			get
			{
				ArrayList columns = new ArrayList();
				foreach(EntityAttribute a in attributes)
				{
					if(a.IsPkColumn)
						columns.Add(a);
				}
				return columns;
			}
		}

		public bool PrimaryKeyIsForeignKey
		{
			get
			{
				foreach(EntityAttribute attr in PkColumns)
				{
				    EntityRelationship rel;

					if(((rel = RelationshipForAttribute(attr)) == null) || (rel.Direction == RelDirection.Parent))
						return false;
				}
				return true;
			}
		}


		public EntityRelationship RelationshipForAttribute(EntityAttribute attr)
		{
			foreach(EntityRelationship rel in Relationships)
			{
				if(attr.ColumnName == rel.LocalKey)
					return rel;
			}
			return null;
		}


		public IList RelationshipsForAttribute(EntityAttribute attr)
		{
			ArrayList relList;

			relList = new ArrayList();
			foreach(EntityRelationship rel in Relationships)
			{
				if(attr.ColumnName == rel.LocalKey)
					relList.Add(rel);
			}
			return relList;
		}


		public IList[] RelationshipSetsForColumns(IList attrList)
		{
			ArrayList[]	lists;
			IList		relList;

			lists = new ArrayList[attrList.Count];
			for(int i = 0; i < attrList.Count; i++)
			{
				lists[i] = new ArrayList();
				relList = RelationshipsForAttribute((EntityAttribute)attrList[i]);
				if(i == 0)
				{
					AddCombinations(new ArrayList(), relList, lists[i]);
				}
				else
				{
					foreach(ArrayList fixedPart in lists[i - 1])
						AddCombinations(fixedPart, relList, lists[i]);
				}
			}
			return (IList[])lists[attrList.Count - 1].ToArray(typeof(IList));
		}


		protected void AddCombinations(IList fixedPart, IList combinations, ArrayList list)
		{
			ArrayList entry;

			foreach(EntityRelationship r in combinations)
			{
				entry = new ArrayList(fixedPart.Count + 1);
				entry.AddRange(fixedPart);
				entry.Add(r);
				list.Add(entry);
			}
		}

		public ICollection ToOneRelationships
		{
			get
			{
				return GetRelationships(RelType.ToOne);
			}
		}

		public ICollection ToManyRelationships
		{
			get
			{
//				ArrayList list = new ArrayList();
//                foreach (EntityRelationship rel in GetRelationships(RelType.ToMany))
//                {
//                    if (rel.ForeignEntity.IsManyToManyTable == false)
//                    {
//                        list.Add(rel);
//                    }
//                }
//                return list;

return  GetRelationships(RelType.ToMany);
			}
		}

        public ICollection ManyToManyRelationships
        {
            get
            {
                ArrayList list = new ArrayList();
                foreach (EntityRelationship rel in GetRelationships(RelType.ToMany))
                {
                    if (rel.ForeignEntity.IsManyToManyTable && rel.ForeignEntity.ChildRelationships.Count == 2)
                    {
                        processManyToMany(rel, list);
                    }
                }
                return list;
            }
        }

        private void processManyToMany(EntityRelationship relx, IList list)
        {
            
            ManyToManyRelation rel = new ManyToManyRelation();
            
            Entity entity = relx.ForeignEntity;
            rel.ManyToManyEntity = entity.ClassName;
            rel.ThisRelationName = relx.InverseRelationship.DotNetName;

            foreach (EntityRelationship relationship in entity.Relationships)
            {
                if (relationship.DotNetName.Equals(relx.InverseRelationship.DotNetName) == false)
                {
                    rel.OtherRelationName = relationship.DotNetName;
                    rel.OtherEntity = relationship.ForeignEntity.className;
                }
            }

            list.Add(rel);
        }

        //--------------------------------------------------------------------------------------
		//	helper
		//--------------------------------------------------------------------------------------

		public bool ColumnIsPrimaryKey(string columnname)
		{
			if(HasAttribute(columnname) == false)
				throw new ArgumentException(String.Format("Table {0} has no column named {1}.", TableName, columnname));
			ArrayList pkcolumns = (ArrayList)PkColumns;
			return ((pkcolumns.Count == 1) && (((EntityAttribute)pkcolumns[0]).ColumnName == columnname));
		}

		
		public bool ColumnIsForeignKey(string columnname)
		{
			if(HasAttribute(columnname) == false)
				throw new ArgumentException(String.Format("Table {0} has no column named {1}.", TableName, columnname));
			EntityRelationship rel = RelationshipForAttribute((EntityAttribute)attributeMap[columnname]);
			return ((rel != null) && (rel.Direction == RelDirection.Child));
		}


		public bool HasAttribute(string columnName)
		{
			return (attributeMap[columnName] != null);
		}


		protected ICollection GetRelationships(RelType types)
		{
			ArrayList list;
			if(types == RelType.All)
			{
				list = relationships;
			}
			else
			{
				list = new ArrayList();
				foreach(EntityRelationship rel in relationships)
				{
					if((rel.Type & types) != 0)
						list.Add(rel);
				}
			}
			return list;
		}

        public override string ToString()
        {
            if (String.IsNullOrEmpty(ClassName))
            {
                return base.ToString();
            }
            return ClassName;
        }

        public string ToXml()
        {
            StringBuilder sb = new StringBuilder();
            string table = "\t" + @"<table name=""#name#"" javaName=""#javaName#"" idMethod=""#idMethod#"" defaultProperty=""#defaultProperty#"" manyToMany=""#manyToMany#"" skipSql=""#skip#"" forceOverwrite=""#forceOverwrite#"" description="""">";
            table = table.Replace("#name#", this.TableName);
            table = table.Replace("#idMethod#", this.IdMethod.ToString().ToLower());
            table = table.Replace("#defaultProperty#", this.DefaultProperty);
            table = table.Replace("#manyToMany#", this.IsManyToManyTable.ToString().ToLower());
            table = table.Replace("#skip#", this.Skip.ToString().ToLower());
            table = table.Replace("#forceOverwrite#", this.ForceOverwrite.ToString().ToLower());
            table = table.Replace("#javaName#", String.IsNullOrEmpty(this.ClassName) ? this.TableName : this.ClassName);
            sb.AppendLine(table);
            foreach (EntityAttribute attribute in Attributes)
            {
                sb.Append(attribute.ToXml());
            }

            Dictionary<String, int> dups = new Dictionary<string, int>();

            foreach (EntityRelationship entityRelationship in this.Relationships)
            {
                sb.Append(entityRelationship.ToXml(dups));
            }

            sb.AppendLine("\t</table>");
            return sb.ToString();
        }
	}

    internal class ManyToManyRelation
    {
        private string manyToManyEntity;
        private string otherEntity;
        private string thisRelationName;
        private string otherRelationName;

        public string ManyToManyEntity
        {
            get { return manyToManyEntity; }
            set { manyToManyEntity = value; }
        }

        public string OtherEntity
        {
            get { return otherEntity; }
            set { otherEntity = value; }
        }

        public string ThisRelationName
        {
            get { return thisRelationName; }
            set { thisRelationName = value; }
        }

        public string OtherRelationName
        {
            get { return otherRelationName; }
            set { otherRelationName = value; }
        }
    }
}
